package com.rengwuxian.coursecoroutines._2_structured_concurrency

import kotlinx.coroutines.CoroutineExceptionHandler
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.async
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlin.coroutines.EmptyCoroutineContext

/**
 * async()对异常的处理
 * async()/await()、异常的结构化管理、CoroutineExceptionHandler
 *
 * 顶层协程是由launch开启的前提下：
 * async的作用：将协程的并行转为串行。使用await拿到结果。
 * async的协程中如果抛出了异常，则不仅会取消整个协程树，还会影响调用await代码所在的协程。
 * 如果兄弟协程抛了异常（除了CancellationException），则自己所在的协程就会被取消。同时那个抛出的异常，会直接抛到最外层的顶级协程中去。否则会抛到线程世界。
 *
 * 在async中抛出异常，会影响两个流程：1.结构化取消协程树（如果是同一个协程树的前提）；2.让await中的协程抛出async中同样的异常（不管是不是一个协程树）。
 *  由于结构化取消，需要传递到父协程后，再取消父协程下的子协程，有这样一个流程。所以CancellationException通常会晚于async中异常的到来。
 *  故而await中的那个协程，是先抛async中的异常，再接受到取消的CancellationException
 *
 * 如果顶层协程是由async开启的，则设置的CoroutineExceptionHandler也无法拦截到异常，更不会抛到最外层的线程世界中。
 *     因为async中发生的异常，要抛给await的那个协程。
 *     因为async本身不是终点，await它的协程才是终点，async只是线性流程的一部分。（await的使用，是另一个世界了）
 *     就是一层层的层级，如此想下去，await传递后，就会看到，最后终归会回到线程世界。（看看终点在哪里就行）
 *
 * CoroutineExceptionHandler是专门为launch准备的（且是顶层而准备的），launch本身就是终点了。
 *
 * 根因：协程的结构化并发
 *
 * 示例：
 *  scope.async/*(handler)*/ {
 *     val deferred = async {
 *       delay(1000)
 *       throw RuntimeException("Error!")
 *     }
 *     launch() {
 *       try {
 *         deferred.await()
 *       } catch (e: Exception) {
 *         println("Caught in await: $e")
 *       }
 *       try {
 *         delay(1000)
 *       } catch (e: Exception) {
 *         println("Caught in delay: $e")
 *       }
 *     }
 * //    delay(100)
 * //    cancel()
 *   }
 * 输出：
 * Caught in await: java.lang.RuntimeException: Error!
 * Caught in delay: kotlinx.coroutines.JobCancellationException: Parent job is Cancelling; job=DeferredCoroutine{Cancelling}@7150eb42
 * 先到达异常，再到达取消。
 */
fun main() = runBlocking<Unit> {
  val scope = CoroutineScope(EmptyCoroutineContext)
  val handler = CoroutineExceptionHandler { _, exception ->
    println("Caught in Coroutine: $exception")
  }
  scope.async/*(handler)*/ {
    val deferred = async {
      delay(1000)
      throw RuntimeException("Error!")
    }
    launch(Job()) {
      try {
        deferred.await()
      } catch (e: Exception) {
        println("Caught in await: $e")
      }
      try {
        delay(1000)
      } catch (e: Exception) {
        println("Caught in delay: $e")
      }
    }
//    delay(100)
//    cancel()
  }
  delay(10000)
}